using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using System.IO;
using System.Xml;
using System.Windows.Forms;

namespace Woz
{
    public sealed class Utilities
    {
        /// <summary>
        /// represents either the x or y axis
        /// </summary>
        public enum Axis { X = 1, Y = 2 };

        /// <summary>
        /// private default constructor
        /// </summary>
        private Utilities()
        {
        }

        /// <summary>
        /// gets the single instance of the variables class
        /// </summary>
        public static Utilities Instance
        {
            get
            {
                // if the instance doesnt exist
                if (sInstance == null)
                {
                    // create it using delayed loading
                    sInstance = new Utilities();
                }

                // return the instance
                return sInstance;
            }
        }
        /// <summary>
        /// parses the passed in string
        /// </summary>
        /// <param name="pColorString">a color string in the format "(xxx,xxx,xxx)" where x is an integer and no quotes</param>
        /// <returns>a color created from the passed in string</returns>
        public Color GetColor(string pColorString)
        {
            int[] colors = new int[3];
            string[] intStrings = pColorString.Split(new char[] { ',' });

            // make sure there are three components
            if (intStrings.Length != 3)
            {
                return Color.Empty;
            }

            // loop through the r g and b
            for (int i = 0; i < 3; i++)
            {
                // check if this string is parsable to an int
                if (!int.TryParse(intStrings[i], out colors[i]))
                {
                    // its not parsable so return an empty color
                    return Color.Empty;
                }
            }

            // if we made it this far then we have valid color ints so send a new color back
            return Color.FromArgb(colors[0], colors[1], colors[2]);
        }

        public int CenterInParent(Control pControl, Axis pAxis)
        {
            if ((pAxis & Axis.X) != 0)
            {
                return pControl.Parent.Width / 2 - pControl.Width / 2;
            }

            return pControl.Parent.Height / 2 - pControl.Height / 2;
        }

        #region File Processing Routines
        
        /// <summary>
        /// the header of each file in a packed file
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet= CharSet.Ansi)]
        private struct PackHeader
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string FileName;

            [MarshalAs(UnmanagedType.I4)]
            public int FileSize;
        }

        /// <summary>
        /// takes any struct with proper packing and sequential layout and converts it to a byte array
        /// </summary>
        /// <param name="pAnything">the struct object to convert</param>
        /// <returns>a byte array of the struct data</returns>
        public byte[] RawSerialize(object pAnything)
        {
            int rawSize = Marshal.SizeOf(pAnything);
            IntPtr buffer = Marshal.AllocHGlobal(rawSize);
            Marshal.StructureToPtr(pAnything, buffer, false);
            byte[] rawData = new byte[rawSize];
            Marshal.Copy(buffer, rawData, 0, rawSize);
            Marshal.FreeHGlobal(buffer);
            
            return rawData;
        }

        /// <summary>
        /// takes a byte array and converts it to an object of any type
        /// </summary>
        /// <param name="pRawData">the raw byte array data</param>
        /// <param name="pAnyType">the type to convert it to</param>
        /// <returns>the converted object</returns>
        public object RawDeserialize(byte[] pRawData, Type pAnyType)
        {
            int rawSize = Marshal.SizeOf(pAnyType);
            
            // quick sanity check
            if (rawSize > pRawData.Length)
                return null;

            IntPtr buffer = Marshal.AllocHGlobal(rawSize);
            Marshal.Copy(pRawData, 0, buffer, rawSize);
            object anything = Marshal.PtrToStructure(buffer, pAnyType);
            Marshal.FreeHGlobal(buffer);

            return anything;
        }

        /// <summary>
        /// packs a list of files into a buffer and returns it
        /// </summary>
        /// <param name="pFiles">the list of files to pack</param>
        /// <returns>the buffer that contains all the files packed into one</returns>
        public byte[] PackFiles(string[] pFiles)
        {
            // create a memory stream to work with the files
            MemoryStream packedFiles = new MemoryStream();

            // first write the number of files in the pack
            packedFiles.WriteByte((byte)pFiles.Length);

            // go through all of the files
            foreach (string file in pFiles)
            {
                // read the contents of the file
                byte[] rawContents = this.ReadFile(file);

                // create the file header and store file name and size
                PackHeader header = new PackHeader();
                header.FileName = Path.GetFileName(file);
                header.FileSize = rawContents.Length;

                // get the raw bytes of the header
                byte[] rawHeader = this.RawSerialize(header);

                // now stuff both the header and contents into the packed files buffer
                packedFiles.Write(rawHeader, 0, rawHeader.Length);
                packedFiles.Write(rawContents, 0, rawContents.Length);
            }

            // now return the bytes of the memory stream
            return packedFiles.ToArray();
        }

        /// <summary>
        /// unpacks a packed buffer containing a whole bunch of files packed into one
        /// and returns the list of file names that were unpacked
        /// </summary>
        /// <param name="pPackedBuffer">the buffer containing the packed contents</param>
        /// <param name="pPath">the path to unpack the files to</param>
        /// <returns>a list of all the full path file names that were unpacked</returns>
        public string[] UnPackFiles(byte[] pPackedBuffer, string pPath)
        {
            // initialize the memory stream
            MemoryStream packedFile = new MemoryStream(pPackedBuffer);
            
            // create a list of file names
            List<string> unpackedFiles = new List<string>();

            // get the number of files
            int numFiles = packedFile.ReadByte();

            // force the path to a fully qualified name
            pPath = Path.GetFullPath(pPath);
            
            // load each file and save it to disk
            for (int i = 0; i < numFiles; i++)
            {
                // read the raw header
                byte[] rawHeader = this.ReadStream(packedFile, Marshal.SizeOf(typeof(PackHeader)));
                PackHeader header = (PackHeader)this.RawDeserialize(rawHeader, typeof(PackHeader));

                // read the contents of the file
                byte[] rawContent = this.ReadStream(packedFile, header.FileSize);

                // now save the file
                string fileName = Path.Combine(pPath, header.FileName.Trim());
                using (BinaryWriter bw = new BinaryWriter(File.Create(fileName)))
                {
                    // write the contents of the file
                    bw.Write(rawContent);
                }

                // save the file name to the list of unpacked files list
                unpackedFiles.Add(fileName);
            }

            // return the list of files
            return unpackedFiles.ToArray();
        }

        /// <summary>
        /// reads the entire contents of a file and returns a byte array containing its contents
        /// </summary>
        /// <param name="pFileName">the file name of the file to read</param>
        /// <returns>a byte array containing the contents of the file</returns>
        public byte[] ReadFile(string pFileName)
        {
            byte[] rawData;
            using (BinaryReader br = new BinaryReader(File.OpenRead(pFileName)))
            {
                rawData = this.ReadStream(br.BaseStream, (int)br.BaseStream.Length);
            }
            return rawData;
        }

        /// <summary>
        /// read given amount of data from a given stream and return the data
        /// </summary>
        /// <param name="pStream">the stream to read data from</param>
        /// <param name="pCount">the amount of data to read from the stream</param>
        /// <returns>the data that was read from the stream</returns>
        /// <exception cref="IOException">throws IOException if unexpected end of stream is encountered</exception>
        public byte[] ReadStream(Stream pStream, int pCount)
        {
            // create the raw data
            byte[] rawData = new byte[pCount];

            // calculate the remaining amount
            int remaining = pCount;
            int offset = 0;
            int read;

            // now read as much as you can
            while (remaining > 0)
            {
                // try reading all of it
                read = pStream.Read(rawData, offset, remaining);

                // check to see how much was read
                if (read == -1)
                {
                    throw new IOException("Unexpected end of stream.");
                }

                // update the remaining
                remaining -= read;
                offset += read;
            }

            // return the raw data
            return rawData;
        }

        /// <summary>
        /// creates an xml writer object and initializes it then returns it
        /// </summary>
        /// <param name="pFilename">the xml file to write xml to</param>
        /// <param name="pRootNode">the root node to save first</param>
        /// <returns>an instance of xml writer object</returns>
        public XmlWriter CreateXmlWriter(string pFilename, string pRootNode)
        {
            // create the settings
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.ConformanceLevel = ConformanceLevel.Document;
            settings.Encoding = Encoding.UTF8;
            settings.Indent = true;

            // create the writer
            XmlWriter writer = XmlWriter.Create(pFilename, settings);

            // write xml start document line
            writer.WriteStartDocument(false);

            // write the root node
            writer.WriteStartElement(pRootNode);
            writer.WriteAttributeString("xmlns", "Woz", null, "urn:Woz");

            // return it
            return writer;
        }

        /// <summary>
        /// creates an xml reader object and initializes it then returns it
        /// </summary>
        /// <param name="pFilename">the name of the file to read xml from</param>
        /// <returns>an instance of the xml reader object</returns>
        public XmlReader CreateXmlReader(string pFilename)
        {
            // create the settings
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ConformanceLevel = ConformanceLevel.Document;
            settings.XmlResolver = null;
            
            // create the reader
            XmlReader reader = XmlReader.Create(pFilename);

            // return it
            return reader;
        }
        #endregion

        /// <summary>
        /// the static instance of this singleton class
        /// </summary>
        private static Utilities sInstance;
    }
}
